/*
	Copyright (C) 2011 Salil Bhagurkar

	This file is part of illusion

	illusion is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as
	published by the Free Software Foundation, either version 3 of
	the License, or (at your option) any later version.

	illusion is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with illusion. 
	If not, see <http://www.gnu.org/licenses/>.
*/

#include <klib/lib.h>
#include <klib/format.h>
#include <klib/memory.h>
#include <kernel/list.h>
#include <kernel/graphics.h>
#include <kernel/errors.h>
#include <kernel/sys.h>
#include <kernel/console.h>
#include <kernel/kmalloc.h>
#include <kernel/timing.h>
#include <kernel/vfs.h>

static struct graphics_driver *drivers, *drivers_tail;

//Maintain the current graphics mode, which will also link to the driver
static struct graphics_mode *current_mode = null;


struct vfs_node *sys_graphics, *sys_modes, *sys_drivers;

static struct vfs_device *graphics_dev;


//The main buffer to which the graphics subsystem draws.
//This buffer is flushed to the driver
static u32 *main_buf;

void register_graphics_driver(struct graphics_driver *driver)
{
	list_attach(drivers, drivers_tail, driver);
}

void unregister_graphics_driver(struct graphics_driver *driver)
{
	list_detach(drivers, drivers_tail, driver);
}
/*
 * Initialize a graphics mode
 */
int g_initialize(uint x, uint y)
{
	struct graphics_driver *driver;
	list_for(drivers, driver) {
		struct graphics_mode *mode;
		list_for(driver->modes, mode) {
			if(mode->x == x && mode->y == y && mode->bits_per_pixel == 32) {
				int err = driver->set_mode(mode);
				if(err) {
					debug("Failed to set mode 0x%x\n", mode->mode_id);
					continue;
				}
				current_mode = mode;
				//Create a 32 bpp buffer by default.
				//The driver also supports only 32 bpp buffers by default
				uint buf_sz = current_mode->x * current_mode->y * 4;
				debug("Creating buffer: %u\n", buf_sz);
				main_buf = (u32 *)kmalloc(buf_sz, "gfxbuf");

				//Create the graphics device
				graphics_dev = vfs_create_device("graphics", 1);
				return 0;
			}
		}
	}
	return ENOENT;
}

int g_deinitialize()
{
	if(current_mode == null)
		return ESTINV;
	//Free the main buffer allocated at the time of mode initialization
	kfree(main_buf, current_mode->x * current_mode->y * 4, "gfxbuf");

	vfs_remove_device(graphics_dev->name, true);
	return current_mode->driver->reset_mode();
}


static void *sys_graphics_read(uint *len)
{
	char *buf = kmalloc(100, "sys");
	format_string(buf, 100, "Illusion Graphics Subsystem\n");
	*len = 100;
	return buf;
}

static uint get_mode_count()
{
	struct graphics_mode *mode;
	uint count = 0;
	struct graphics_driver *driver;
	list_for(drivers, driver) {
		list_for((driver->modes), mode) {
			count++;
		}
	}
	return count;
}

static void *sys_modes_read(uint *len)
{
	uint mode_count = get_mode_count();
	*len = mode_count * sizeof(struct graphics_mode);
	void *buf = (void *)kmalloc(*len, "sys");
	struct graphics_driver *driver;
	struct graphics_mode *mode;
	uint i = 0;
	list_for(drivers, driver) {
		list_for((driver->modes), mode) {
			memcpy(buf + i, mode, sizeof(struct graphics_mode));
			i += sizeof(struct graphics_mode);
		}
	}
	return buf;
}

static int default_write(void *buf, uint len)
{
	return ENOSUP;
}


static u32 construct_rgb(struct ilg_pixel *pixel)
{
	return pixel->r | (pixel->g << 8) | (pixel->b << 16);
}

static void construct_ilg_pixel(struct ilg_pixel *pixel, u32 rgb)
{
	pixel->r = (u8)(rgb & 0xff);
	pixel->g = (u8)((rgb >> 8) & 0xff);
	pixel->b = (u8)((rgb >> 16) & 0xff);
}

static u8 make_transparent(u8 prev, u8 next, u8 a)
{
	return ((prev) * (255 - a)) / 255 + ((next) * a) / 255;
}

static void flush_ilg(struct ilg_header *ilg_header)
{
	uint x, y, i, j;
	for(y = ilg_header->top, j = 0;
			y < (ilg_header->top + ilg_header->height); y++, j++) {
		for(x = ilg_header->left, i = 0
				; x < (ilg_header->left + ilg_header->width); x++, i++) {
			//Construct the target buffer index from the mode parameters
			uint lin_index_dest = y * current_mode->x + x;
			uint lin_index_src = j * ilg_header->width + i;

			//Already present and to be written pixel
			struct ilg_pixel *next = &ilg_header->pixels[lin_index_src];
			struct ilg_pixel prev;
			construct_ilg_pixel(&prev, main_buf[lin_index_dest]);

			//Construct each color
			next->r = make_transparent(prev.r, next->r, next->a);
			next->g = make_transparent(prev.g, next->g, next->a);
			next->b = make_transparent(prev.b, next->b, next->a);

			main_buf[lin_index_dest] = construct_rgb(next);

		}
	}
}

static int flush_node(struct vfs_node *node)
{
	//Recursively flush all chilldren first
	struct vfs_node *child = vfs_get_child(node);
	while(child) {
		int err = flush_node(child);
		if(err)
			return err;
		child = vfs_next_child(child);
	}
	//Flush this node

	//Read in the ilg
	struct ilg_header *ilg_header = (struct ilg_header *)kmalloc(node->length, "ilgh");
	vfs_read(node, "data", ilg_header, 0, node->length);
	debug("Writing image: %u x %u\n", ilg_header->width, ilg_header->height);
	flush_ilg(ilg_header);
	kfree(ilg_header, node->length, "ilgh");
	return 0;
}

int g_flush()
{
	struct vfs_node *child = vfs_get_child(graphics_dev->root);
	while(child) {
		int err = flush_node(child);
		if(err)
			return err;
		child = vfs_next_child(child);
	}
	//Write it to the driver
	current_mode->driver->flush_buffer(main_buf);
	return 0;
}

int graphics_sys_init()
{
	initmsg("Creating graphics interfaces..\n");
	sys_graphics = vfs_create(sys_root, "graphics");
	register_sys_interface(sys_graphics, sys_graphics_read, default_write);
	sys_modes = vfs_create(sys_graphics, "modes");
	register_sys_interface(sys_modes, sys_modes_read, default_write);
	sys_drivers = vfs_create(sys_graphics, "drivers");

	return 0;
}
